﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Microsoft.Win32;
using System.IO;

namespace CodePlex.Resourceful.Microsoft.SSDS
{

    public class SSDSConnection
    {
        private readonly string _username;
        private readonly string _password;
        private readonly bool _useSSL;

        private readonly Tracer _tracer;
       
        public SSDSConnection(string username, string password, bool useSSL)
        {
            if (string.IsNullOrEmpty(username))
            {
                throw new ArgumentException("Username cannot be blank.","username");
            }
            if (string.IsNullOrEmpty(password))
            {
                throw new ArgumentException("Password cannot be blank.", "password");
            }
            _username = username;
            _password = password;
            _useSSL = useSSL;

            _tracer = new Tracer();
        }

        public Tracer Tracer
        {
            get
            {
                return _tracer;
            }
        }

        public bool IsSSL
        {
            get
            {
                return _useSSL;
            }
        }

        public string Username
        {
            get
            {
                return _username;
            }
        }


        private SSDSClient Client
        {
            get
            {
                SSDSClient rt = new SSDSClient(_username, _password, _useSSL);
                rt.Tracer.AddListener(_tracer);
                return rt;
            }
        }


        public static void RegisterMimeType()
        {
            //  In Registry go to [HKEY_CLASSES_ROOT\MIME\Database\Content Type
            // Add new key "application/x-ssds+xml".
            // Add a new string value (Extension=".xml").

            RegistryKey rk = Registry.ClassesRoot.OpenSubKey(@"\MIME\Database\Content Type", true).CreateSubKey(MediaType.ApplicationExSsdsXml);

            rk.SetValue("Extension", ".xml", RegistryValueKind.String);

        }



        // authority (create, get, get containers)

        public void CreateAuthority(string authority)
        {
            ClientResponse response = this.Client.CreateAuthority(authority);
            this.Assert(response, HttpStatus.Created);
        }

        public Authority GetAuthority(string authority)
        {
            ClientResponse response = this.Client.GetAuthority(authority);
            this.Assert(response, HttpStatus.OK);
            return Authority.Parse(response.AsXmlReader());

        }


        public IEnumerable<Container> GetContainers(string authority)
        {
            return this.GetContainers(authority, (string)null);
        }
        public IEnumerable<Container> GetContainers(string authority, SSDSB whereClause)
        {
            return this.GetContainers(authority, whereClause.ToQueryString());
        }
        public IEnumerable<Container> GetContainers(string authority, string whereClause)
        {

            ClientResponse response = this.Client.QueryContainers(authority,whereClause);
            this.Assert(response, HttpStatus.OK);

            XmlReader reader = response.AsXmlReader();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "EntitySet" && reader.NamespaceURI == SSDSConstants.Namespaces.Sitka)
                {
                    yield break;
                }

                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.LocalName == "Container" && reader.NamespaceURI == SSDSConstants.Namespaces.Sitka)
                    {
                        yield return Container.Parse(reader);
                    }
                }
            }

        }

        public bool ContainerExists(string authority, string containerID)
        {
            ClientResponse response = this.Client.GetContainer(authority, containerID);
            if (response.HttpStatus.Equals(HttpStatus.OK))
            {
                return true;
            }
            if (response.HttpStatus.Equals(HttpStatus.NotFound))
            {
                return false;
            }
            throw this.Fail(response);
        }

        public bool EntityExists(string authority, string containerID, string entityID)
        {
            ClientResponse response = this.Client.GetEntity(authority, containerID, entityID);
            if (response.HttpStatus.Equals(HttpStatus.OK))
            {
                return true;
            }
            if (response.HttpStatus.Equals(HttpStatus.NotFound))
            {
                return false;
            }
            throw this.Fail(response);
        }





        // container (create, delete, get, get entities)
        public void CreateContainer(string authority, string containerID)
        {
            ClientResponse response = this.Client.CreateContainer(authority, containerID);
            this.Assert(response, HttpStatus.Created);

        }

        public void DeleteContainer(string authority, string containerID)
        {
            ClientResponse response = this.Client.DeleteContainer(authority, containerID);
            this.Assert(response, HttpStatus.OK);

        }

        public Container GetContainer(string authority, string containerID)
        {
            ClientResponse response = this.Client.GetContainer(authority, containerID);
            this.Assert(response, HttpStatus.OK);
            return Container.Parse(response.AsXmlReader());
        }

        public IEnumerable<Entity> GetEntities(string authority, string containerID)
        {
            return GetEntities(authority, containerID, (string)null);
        }
        public IEnumerable<Entity> GetEntities(string authority, string containerID, SSDSB whereClause)
        {
            return GetEntities(authority, containerID, whereClause.ToQueryString());
        }

        public IEnumerable<Entity> GetEntities(string authority, string containerID, string whereClause)
        {
            ClientResponse response = this.Client.QueryEntities(authority, containerID, whereClause);
            this.Assert(response, HttpStatus.OK);

            XmlReader reader = response.AsXmlReader();
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "EntitySet" && reader.NamespaceURI == SSDSConstants.Namespaces.Sitka)
                {
                    yield break;
                }

                if (reader.NodeType == XmlNodeType.Element && reader.NamespaceURI == string.Empty)
                {
                    yield return Entity.Parse(reader.LocalName, reader);
                }

            }
        }







     

       // entity (create, delete, get, update)

        public void CreateEntity(string authority, string containerID, string entityID, string kind, params FlexibleProperty[] properties)
        {
            ClientResponse response = this.Client.CreateEntity(authority, containerID, entityID, kind, properties);
            this.Assert(response,HttpStatus.Created);
        }

        public void DeleteEntity(string authority, string containerID, string entityID)
        {
            ClientResponse response = this.Client.DeleteEntity(authority, containerID, entityID);
            this.Assert(response, HttpStatus.OK);
        }
        public void DeleteEntityIfExists(string authority, string containerID, string entityID)
        {
            if (this.EntityExists(authority, containerID, entityID))
            {
                this.DeleteEntity(authority, containerID, entityID);
            }
        }

        public Entity GetEntity(string authority, string containerID, string entityID)
        {
            ClientResponse response = this.Client.GetEntity(authority, containerID, entityID, 
                NameValuePairs.From(HttpHeader.Accept,MediaType.ApplicationExSsdsXml));
            this.Assert(response, HttpStatus.OK);
           
            return Entity.Parse(response.AsXmlReader());
        }

        public void UpdateEntity(string authority, string containerID, string entityID, string kind, params FlexibleProperty[] properties)
        {
            ClientResponse response = this.Client.UpdateEntity(authority, containerID, entityID, kind, properties);
            this.Assert(response, HttpStatus.OK);

        }


        // blob entity
        public void CreateEntityBlob(string authority, string containerID, string entityID, string contentType, long contentLength, string contentDisposition, Stream input)
        {
            this.CreateEntityBlob(authority, containerID, entityID, contentType, contentLength, contentDisposition, input, null);
        }
        public void CreateEntityBlob(string authority, string containerID, string entityID, string contentType, long contentLength, string contentDisposition, Stream input, Action<long> bytesTransferred)
        {
            ClientResponse response = this.Client.CreateEntityBlob(authority, containerID, entityID, contentType, contentLength, contentDisposition, input, bytesTransferred);
            this.Assert(response, HttpStatus.Created);
        }
        public void UpdateEntityBlob(string authority, string containerID, string entityID, string contentType, long contentLength, string contentDisposition, Stream input)
        {
            this.UpdateEntityBlob(authority, containerID, entityID, contentType, contentLength, contentDisposition, input, null);
        }
        public void UpdateEntityBlob(string authority, string containerID, string entityID, string contentType, long contentLength, string contentDisposition, Stream input, Action<long> bytesTransferred)
        {
            ClientResponse response = this.Client.UpdateEntityBlob(authority, containerID, entityID, contentType, contentLength, contentDisposition, input, bytesTransferred);
            this.Assert(response, HttpStatus.OK);
        }

      
        public string GetEntityBlobAsString(string authority, string containerID, string entityID)
        {
            ClientResponse response = this.Client.GetEntity(authority, containerID, entityID);
            this.Assert(response, HttpStatus.OK);
            return response.AsString();
        }

        public Stream GetEntityBlobAsStream(string authority, string containerID, string entityID)
        {
            ClientResponse response = this.Client.GetEntity(authority, containerID, entityID,false);
            this.Assert(response, HttpStatus.OK);
            return response.AsStream();
        }




        private Exception Fail(ClientResponse response)
        {
            string contentType = response.Headers.GetIfExists(HttpHeader.ContentType);
            if (contentType != null && contentType.Contains(MediaType.ApplicationExSsdsXml))
            {
                Error error = Error.Parse(response.AsXmlReader());

                return new ApplicationException(error.Code + " " + error.Message);
            }
            return new ApplicationException("unknown response");
        }


        private void Assert(ClientResponse response, HttpStatus expectedStatus)
        {
            if (!response.HttpStatus.Equals(expectedStatus))
            {
                throw this.Fail(response);
            }
        }









    }


   

}
